package com.example.yuanboot.service.common.impl;

import com.example.yuanboot.common.constant.RedisConstant;
import com.example.yuanboot.service.common.IRedisService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;


@Slf4j
@RequiredArgsConstructor
@Service
public class RedisServiceImpl implements IRedisService {

    private final RedisTemplate<Object, Object> redisTemplate;

    @Override
    public void set(String key, Object value, long time) {
        try {
            redisTemplate.opsForValue().set(getValueKey(key), value, time, TimeUnit.SECONDS);
        } catch (Throwable t) {
            log.error("redis set method error key:[{}] value:[{}]", key, value, t);
        }
    }

    @Override
    public void set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(getValueKey(key), value);
        } catch (Throwable t) {
            log.error("redis set method error key:[{}] value:[{}]", key, value, t);
        }
    }

    @Override
    public Object get(String key) {
        try {
            return redisTemplate.opsForValue().get(getValueKey(key));
        } catch (Throwable t) {
            log.error("redis get method error key:[{}]", key, t);
        }
        return null;
    }

    @Override
    public Boolean del(String key) {
        try {
            return redisTemplate.delete(key);
        } catch (Throwable t) {
            log.error("redis del method error key:[{}]", key, t);
        }
        return Boolean.FALSE;
    }

    @Override
    public Long del(List<Object> keys) {
        try {
            return redisTemplate.delete(keys);
        } catch (Throwable t) {
            log.error("redis del method error keys:[{}]", keys, t);
        }
        return 0L;
    }

    @Override
    public Boolean expire(String key, long time) {
        try {
            return redisTemplate.expire(key, time, TimeUnit.SECONDS);
        } catch (Throwable t) {
            log.error("redis expire method error key:[{}]", key, t);
        }
        return Boolean.FALSE;
    }

    @Override
    public Long getExpire(String key) {
        try {
            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
        } catch (Throwable t) {
            log.error("redis getExpire method error key:[{}]", key, t);
        }
        return 0L;
    }

    @Override
    public Boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Throwable t) {
            log.error("redis hasKey method error key:[{}]", key, t);
        }
        return Boolean.FALSE;
    }

    @Override
    public Long incr(String key, long delta) {
        try {
            return redisTemplate.opsForValue().increment(getValueKey(key), delta);
        } catch (Throwable t) {
            log.error("redis incr method error key:[{}]", key, t);
        }
        return 0L;
    }

    @Override
    public Long decr(String key, long delta) {
        try {
            return redisTemplate.opsForValue().increment(getValueKey(key), -delta);
        } catch (Throwable t) {
            log.error("redis decr method error key:[{}]", key, t);
        }
        return 0L;
    }

    @Override
    public Object hGet(String key, String hashKey) {
        try {
            return redisTemplate.opsForHash().get(getHashKey(key), hashKey);
        } catch (Throwable t) {
            log.error("redis hGet method error key:[{}], hashKey:[{}]", key, hashKey, t);
        }
        return null;
    }

    @Override
    public Boolean hSet(String key, String hashKey, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(getHashKey(key), hashKey, value);
            return expire(getHashKey(key), time);
        } catch (Throwable t) {
            log.error("redis hSet method error key:[{}], hashKey:[{}], value:[{}]", key, hashKey, value, t);
        }
        return Boolean.FALSE;
    }

    @Override
    public void hSet(String key, String hashKey, Object value) {
        try {
            redisTemplate.opsForHash().put(getHashKey(key), hashKey, value);
        } catch (Throwable t) {
            log.error("redis hSet method error key:[{}], hashKey:[{}], value:[{}]", key, hashKey, value, t);
        }
    }

    @Override
    public Map<Object, Object> hGetAll(String key) {
        try {
            return redisTemplate.opsForHash().entries(getHashKey(key));
        } catch (Throwable t) {
            log.error("redis hGetAll method error key:[{}]", key, t);
        }
        return null;
    }

    @Override
    public Boolean hSetAll(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(getHashKey(key), map);
            return expire(getHashKey(key), time);
        } catch (Throwable t) {
            log.error("redis hSetAll method error key:[{}], map:[{}]", key, map, t);
        }
        return Boolean.FALSE;
    }

    @Override
    public void hSetAll(String key, Map<String, ?> map) {
        try {
            redisTemplate.opsForHash().putAll(getHashKey(key), map);
        } catch (Throwable t) {
            log.error("redis hSetAll method error key:[{}], map:[{}]", key, map, t);
        }
    }

    @Override
    public void hDel(String key, Object... hashKey) {
        try {
            redisTemplate.opsForHash().delete(getHashKey(key), hashKey);
        } catch (Throwable t) {
            log.error("redis hDel method error key:[{}], hashKey:[{}]", key, hashKey, t);
        }
    }

    @Override
    public Boolean hHasKey(String key, String hashKey) {
        try {
            return redisTemplate.opsForHash().hasKey(getHashKey(key), hashKey);
        } catch (Throwable t) {
            log.error("redis hHasKey method error key:[{}], hashKey:[{}]", key, hashKey, t);
        }
        return Boolean.FALSE;
    }

    @Override
    public Long hIncr(String key, String hashKey, Long delta) {
        try {
            return redisTemplate.opsForHash().increment(getHashKey(key), hashKey, delta);
        } catch (Throwable t) {
            log.error("redis hIncr method error key:[{}], hashKey:[{}]", key, hashKey, t);
        }
        return 0L;
    }

    @Override
    public Long hDecr(String key, String hashKey, Long delta) {
        try {
            return redisTemplate.opsForHash().increment(getHashKey(key), hashKey, -delta);
        } catch (Throwable t) {
            log.error("redis hDecr method error key:[{}], hashKey:[{}]", key, hashKey, t);
        }
        return 0L;
    }

    @Override
    public Set<Object> sMembers(String key) {
        try {
            return redisTemplate.opsForSet().members(getSetKey(key));
        } catch (Throwable t) {
            log.error("redis sMembers method error key:[{}]", key, t);
        }
        return ImmutableSet.of();
    }

    @Override
    public Long sAdd(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(getSetKey(key), values);
        } catch (Throwable t) {
            log.error("redis sAdd method error key:[{}], values:[{}]", key, values, t);
        }
        return 0L;
    }

    @Override
    public Long sAdd(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(getSetKey(key), values);
            expire(getSetKey(key), time);
            return count;
        } catch (Throwable t) {
            log.error("redis sAdd method error key:[{}], values:[{}]", key, values, t);
        }
        return 0L;
    }

    @Override
    public Boolean sIsMember(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(getSetKey(key), value);
        } catch (Throwable t) {
            log.error("redis sIsMember method error key:[{}], value:[{}]", key, value, t);
        }
        return Boolean.FALSE;
    }

    @Override
    public Long sSize(String key) {
        try {
            return redisTemplate.opsForSet().size(getSetKey(key));
        } catch (Throwable t) {
            log.error("redis sSize method error key:[{}]", key, t);
        }
        return 0L;
    }

    @Override
    public Long sRemove(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().remove(getSetKey(key), values);
        } catch (Throwable t) {
            log.error("redis sRemove method error key:[{}], values:[{}]", key, values, t);
        }
        return 0L;
    }

    @Override
    public List<Object> lRange(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(getListKey(key), start, end);
        } catch (Throwable t) {
            log.error("redis lRange method error key:[{}]", key, t);
        }
        return ImmutableList.of();
    }

    @Override
    public Long lSize(String key) {
        try {
            return redisTemplate.opsForList().size(getListKey(key));
        } catch (Throwable t) {
            log.error("redis lSize method error key:[{}]", key, t);
        }
        return 0L;
    }

    @Override
    public Object lIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(getListKey(key), index);
        } catch (Throwable t) {
            log.error("redis lIndex method error key:[{}]", key, t);
        }
        return null;
    }

    @Override
    public Object llPop(String key) {
        try {
            return redisTemplate.opsForList().leftPop(getListKey(key));
        } catch (Throwable t) {
            log.error("redis lPop method error key:[{}]", key, t);
        }
        return null;
    }

    @Override
    public Long lPush(String key, Object value) {
        try {
            return redisTemplate.opsForList().rightPush(getListKey(key), value);
        } catch (Throwable t) {
            log.error("redis lPush method error key:[{}], value:[{}]", key, value, t);
        }
        return 0L;
    }

    @Override
    public Long lPush(String key, Object value, long time) {
        try {
            Long index = redisTemplate.opsForList().rightPush(getListKey(key), value);
            expire(getListKey(key), time);
            return index;
        } catch (Throwable t) {
            log.error("redis lPush method error key:[{}], value:[{}]", key, value, t);
        }
        return 0L;
    }

    @Override
    public Long lPushAll(String key, Object... values) {
        try {
            return redisTemplate.opsForList().rightPushAll(getListKey(key), values);
        } catch (Throwable t) {
            log.error("redis lPushAll method error key:[{}], values:[{}]", key, values, t);
        }
        return 0L;
    }

    @Override
    public Long lPushAll(String key, Long time, Object... values) {
        try {
            Long count = redisTemplate.opsForList().rightPushAll(getListKey(key), values);
            expire(getListKey(key), time);
            return count;
        } catch (Throwable t) {
            log.error("redis lPushAll method error key:[{}], values:[{}]", key, values, t);
        }
        return 0L;
    }

    @Override
    public Long lRemove(String key, long count, Object value) {
        try {
            return redisTemplate.opsForList().remove(getListKey(key), count, value);
        } catch (Throwable t) {
            log.error("redis lRemove method error key:[{}], value:[{}]", key, value, t);
        }
        return 0L;
    }

    @Override
    public List<String> scan(String patternKey) {
        try {

            ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
            RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
            RedisConnection rc = Objects.requireNonNull(factory).getConnection();
            Cursor<byte[]> cursor = rc.scan(options);
            List<String> result = new ArrayList<>();
            while (cursor.hasNext()) {
                result.add(new String(cursor.next()));
            }
            cursor.close();
            RedisConnectionUtils.releaseConnection(rc, factory, false);
            return result;
        } catch (Throwable t) {
            log.error("redis scan method error patternKey:[{}]", patternKey, t);
        }
        return ImmutableList.of();
    }

    @Override
    public String getValueKey(String key) {
        return RedisConstant.VALUE_PREFIX + key;
    }

    @Override
    public String getHashKey(String key) {
        return RedisConstant.HASH_PREFIX + key;
    }

    @Override
    public String getSetKey(String key) {
        return RedisConstant.SET_PREFIX + key;
    }

    @Override
    public String getListKey(String key) {
        return RedisConstant.LIST_PREFIX + key;
    }
}
